#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@IDE     ：PyCharm 
@Author  ：kiway
@Date    ：2024/5/1 10:09 
"""

import pandas as pd

import ccxt.pro
# from ccxt.async_support.base.exchange import Exchange
from .BaseEngine import BaseEngine
from ..logger import pretty_print
from .BaseEngine import (EVENT_PsORDER, PsOrderEvent, OrderStatusEvent)

import traceback
import multiprocessing
import asyncio
from ..config import config
from .DataPersist import TradeRecord, LatestRecord
import time
from ..dataloader import process_filter
from decimal import Decimal

exchange = getattr(ccxt.pro, config.exchange_id)({
    'options': config.exchange_options,
    'apiKey': config.exchange_apikey,
    'secret': config.exchange_secret,
})

exchange.set_sandbox_mode(config.sandbox_mode)


class LocalRecord:

    def __init__(self):
        server_return_orders_df_columns = ["订单id", "币种", "下单方向", "订单类型", "最新状态", "下单量", "已完成量",
                                           "平均成交价", "手续费", "更新时间"]

        self.server_return_orders_df = pd.DataFrame(columns=server_return_orders_df_columns)

        server_feedback_columns = ["订单id", "币种", "下单方向", "订单类型", "限价单价格", "市价单平均价", "下单量",
                                   "已完成量",
                                   "更新时间"]

        self.server_feedback_df = pd.DataFrame(columns=server_feedback_columns)

    @property
    def server_return_orders_df_length(self):
        return len(self.server_return_orders_df)

    @property
    def server_feedback_df_length(self):
        return len(self.server_feedback_df)

    def process_update_server_return_orders(self, data):
        """处理推送的orders 信息"""

        return_orders_info = data[0]["info"]
        order_id = return_orders_info["i"]  # 交易所的订单id
        symbol = data[0]["symbol"]  # 下单币种
        place_side = return_orders_info["S"]  # 下单方向

        order_type = return_orders_info["o"]  # 订单类型，市价单or现价单
        order_current_status = return_orders_info["X"]  # 订单目前最新状态
        instruct_quantity = return_orders_info["q"]  # 下单量，以合约数量张为单位
        order_current_filled_amount = return_orders_info["l"]  # 订单目前已经完成的数量，合约张数为单位
        order_matchmaking_average_price = data[0]["average"]  # 订单成交平均价格
        order_execuate_fee = data[0]["fee"]["cost"] if data[0]["fee"] else None  # 订单执行手续费，以USDT计价
        order_current_update_time = data[0]["timestamp"]  # 更新时间， 交易所返回的

        if len(self.server_return_orders_df[self.server_return_orders_df["订单id"] == order_id]) < 1:
            self.server_return_orders_df.loc[self.server_return_orders_df_length] = [
                order_id,
                symbol,
                place_side,
                order_type,
                order_current_status,
                instruct_quantity,
                order_current_filled_amount,
                order_matchmaking_average_price,
                order_execuate_fee,
                order_current_update_time
            ]

        elif len(self.server_return_orders_df[self.server_return_orders_df["订单id"] == order_id]) > 1:
            pretty_print(f"订单信息更新错误，订单为{data}")

        else:
            self.server_return_orders_df[self.server_return_orders_df["订单id"] == order_id] = [
                order_id,
                symbol,
                place_side,
                order_type,
                order_current_status,
                instruct_quantity,
                order_current_filled_amount,
                order_matchmaking_average_price,
                order_execuate_fee,
                order_current_update_time
            ]

    def process_place_order_feedback(self, data):

        order_id = data["info"]["orderId"]
        symbol = data["info"]["symbol"]
        order_side = data["info"]["side"]
        order_type = data["info"]["type"]
        order_instruct_price = data["info"]["price"]
        order_exec_avgprice = data["info"]["avgPrice"]
        order_instruct_qty = data["info"]["origQty"]
        order_exec_qty = data["info"]["executedQty"]

        updatetime = data["info"]["updateTime"]

        self.server_feedback_df.loc[self.server_feedback_df_length] = [
            order_id,
            symbol,
            order_side,
            order_type,
            order_instruct_price,
            order_exec_avgprice,
            order_instruct_qty,
            order_exec_qty,
            updatetime
        ]

    def __del__(self):
        self.server_feedback_df.to_csv("下单返回测试数据.csv", index=False)
        self.server_return_orders_df.to_csv("订单状态监控数据.csv", index=False)


class Portfolio:
    """实现一些基本的下单数量计算"""

    @classmethod
    def calculate_position_ratio_by_symbol(cls, balance_info, position_info):
        """基于资产余额以及持仓计算 仓位中每个symbol的仓位比例"""

        po_info = {}
        if len(position_info) > 0:
            for info in position_info:
                position = {"unrealizedPnl": info["unrealizedPnl"],
                            "initialMargin": info["initialMargin"],
                            "side": info["side"],
                            "occupied": info["unrealizedPnl"] + info["initialMargin"],
                            "position_qty": info["contracts"] * info["contractSize"],
                            "notional": info["notional"]}

                po_info[info["symbol"]] = position

        usdt_balance = None

        for asset in balance_info["info"]:
            if len(asset) == 0:
                raise ValueError("error occur in balance info update")
            if asset["asset"] == "USDT":
                usdt_balance = float(asset["crossWalletBalance"]) + float(asset["crossUnPnl"])
                break

        if usdt_balance is None:
            raise ValueError("USDT Balance INFO get nothing")
        # pretty_print(f"USDT Balance is {usdt_balance}")
        return po_info, usdt_balance * config.buffer_balance_ratio

    @classmethod
    def close_position(cls, po_info, trading_ruls):
        """平仓"""

        fine_qty_ls = []

        proposed_qty = po_info["position_qty"]

        if proposed_qty >= trading_ruls["maxQty"]:
            # 下单数量超过最大量
            order_num = int(po_info["position_qty"] / trading_ruls["maxQty"])
            for i in range(order_num):
                fine_qty_ls.append(trading_ruls["maxQty"])
                proposed_qty -= trading_ruls["maxQty"]

            fine_qty_ls.append(proposed_qty)

        else:
            fine_qty_ls.append(proposed_qty)

        return fine_qty_ls

    @classmethod
    def calculate_bassAssetQty_by_usdtAmount(cls, symbol, trading_ruls, maket_price, gap_usdt):
        """根据需要下单的USDT数量来倒推出需要下单的基础币的数量"""

        fine_qty_ls = []
        proposed_usdt = gap_usdt * config.leverage
        print(f"{symbol} 杠杆计算后的名义价值 {proposed_usdt}")
        # print(f"proposed_usdt is {proposed_usdt}")
        # print(f"trading_ruls_min_notional is {trading_ruls['min_notional']}")

        if proposed_usdt < trading_ruls["min_notional"]:
            if proposed_usdt < trading_ruls["min_notional"] * 0.5:
                # 订单名义价值过小(没有过半)，不下单
                tuning_usdt = 0.
            else:
                # 下单量过半，直接按照最小名义价值下单
                tuning_usdt = trading_ruls["min_notional"]

        else:
            tuning_usdt = proposed_usdt

        proposed_qty = tuning_usdt / maket_price

        if proposed_qty >= trading_ruls["maxQty"]:
            # 下单数量超过最大量
            order_num = int(proposed_qty / trading_ruls["maxQty"])
            for i in range(order_num):
                fine_qty_ls.append(trading_ruls["maxQty"])
                proposed_qty -= trading_ruls["maxQty"]

            num = Decimal(str(proposed_qty))
            fine_qty_ls.append(float(num.quantize(Decimal(str(trading_ruls["minQty"])))))

        else:
            num = Decimal(str(proposed_qty))
            fine_qty_ls.append(float(num.quantize(Decimal(str(trading_ruls["minQty"])))))

        return fine_qty_ls


class TradingEngine(BaseEngine):
    """经过风控之后的下单模块"""

    def __init__(self, main_engine):
        super().__init__("交易引擎", main_engine)
        self.local_record = LocalRecord()
        self.exchange = exchange
        self.trading_rules = process_filter()

        # self.position_info_df = None
        # self.balance_info_df = None
        # self.order_status_df = None
        self.portfolio = Portfolio()

    def on_event(self):
        while True:
            try:
                event = self._msg_q.get()

                # if event.type == EVENT_BALANCE_UPDATE:
                #     self.balance_info_df = event.data
                #
                # elif event.type == EVENT_POSITIONS_UPDATE:
                #     self.position_info_df = event.data
                #
                # elif event.type == EVENT_ORDER_STATUS_UPDATE:
                #     self.order_status_df = event.data

                if event.type == EVENT_PsORDER:
                    print("xia dan hui diao")

                    order_data, flag_symbol, po_info = self.create_order_pre(event)
                    self.create_order_pos(order_data, flag_symbol, po_info)

            except:
                traceback.print_exc()

    async def test(self):
        print("da yin test data")

    def create_order_pre(self, event: PsOrderEvent):
        """创建订单
        轧差第一步，先将头寸多余部分进行平仓获取usdt
        """

        record = LatestRecord()

        loop = asyncio.get_event_loop()

        tasks = []

        order_data = event.data
        # print(f"position_info is {record.position_info()}")
        po_info, usdt_balance = Portfolio.calculate_position_ratio_by_symbol(record.balance_info(),
                                                                             record.position_info())

        order_data["target_amount"] = order_data["value"] * usdt_balance

        flag_symbol = []

        for idx, row in order_data.iterrows():
            if idx not in po_info:
                continue

            else:
                if row["target_amount"] >= 0:
                    if po_info[idx]["side"] == "long":
                        if row["target_amount"] - po_info[idx]["occupied"] < 0:
                            # 需要平掉一部分 多头 头寸
                            amount_ls = Portfolio.calculate_bassAssetQty_by_usdtAmount(idx, self.trading_rules[idx],
                                                                                       row["price"],
                                                                                       po_info[idx]["occupied"] - row[
                                                                                           "target_amount"])

                            for amount in amount_ls:
                                if amount > 0.00001:
                                    data = {"symbol": idx, "orderside": "sell", "amount": str(amount)}
                                    pretty_print(f"1  {data}")
                                    tasks.append(loop.create_task(self.exc_place_order(**data)))

                            flag_symbol.append(idx)

                    if po_info[idx]["side"] == "short":
                        # 直接平掉空头头寸
                        amount_ls = Portfolio.close_position(po_info[idx], self.trading_rules[idx])

                        for amount in amount_ls:
                            if amount > 0.00001:
                                data = {"symbol": idx, "orderside": "buy", "amount": str(amount)}
                                pretty_print(f"2  {data}")
                                tasks.append(loop.create_task(self.exc_place_order(**data)))

                else:
                    if po_info[idx]["side"] == "short":
                        if abs(row["target_amount"]) - po_info[idx]["occupied"] < 0:
                            # 需要平掉一部分 空头 头寸

                            amount_ls = Portfolio.calculate_bassAssetQty_by_usdtAmount(idx, self.trading_rules[idx],
                                                                                       row["price"],
                                                                                       po_info[idx]["occupied"] - abs(
                                                                                           row["target_amount"]))
                            for amount in amount_ls:
                                if amount > 0.00001:
                                    data = {"symbol": idx, "orderside": "buy", "amount": str(amount)}
                                    pretty_print(f"3  {data}")
                                    tasks.append(loop.create_task(self.exc_place_order(**data)))

                            flag_symbol.append(idx)

                    if po_info[idx]["side"] == "long":
                        # 直接平掉多头头寸
                        amount_ls = Portfolio.close_position(po_info[idx], self.trading_rules[idx])

                        for amount in amount_ls:
                            if amount > 0.00001:
                                data = {"symbol": idx, "orderside": "sell", "amount": str(amount)}
                                pretty_print(f"4  {data}")
                                tasks.append(loop.create_task(self.exc_place_order(**data)))

        if len(tasks) > 0:
            loop.run_until_complete(asyncio.wait(tasks))

        return order_data, flag_symbol, po_info

    def create_order_pos(self, order_data, flag_symbol, po_info):
        """创建订单
        轧差第二步，将第一步获取的usdt去进行下单
        """

        loop = asyncio.get_event_loop()

        tasks = []
        for idx, row in order_data.iterrows():
            if idx in flag_symbol:
                continue

            if idx not in po_info:
                occupied = 0

            else:
                occupied = po_info[idx]["occupied"]

            if row["target_amount"] >= 0:
                amount_ls = Portfolio.calculate_bassAssetQty_by_usdtAmount(idx, self.trading_rules[idx], row["price"],
                                                                           row["target_amount"] - occupied)
                for amount in amount_ls:
                    if amount > 0.00001:
                        data = {"symbol": idx, "orderside": "buy", "amount": str(amount)}
                        pretty_print(f"5  {data}")
                        tasks.append(loop.create_task(self.exc_place_order(**data)))

            else:
                amount_ls = Portfolio.calculate_bassAssetQty_by_usdtAmount(idx, self.trading_rules[idx], row["price"],
                                                                           abs(row["target_amount"]) - occupied)

                for amount in amount_ls:
                    if amount > 0.00001:
                        data = {"symbol": idx, "orderside": "sell", "amount": str(amount)}
                        pretty_print(f"6  {data}")
                        tasks.append(loop.create_task(self.exc_place_order(**data)))

        if len(tasks) > 0:
            loop.run_until_complete(asyncio.wait(tasks))

    def process_pending_order(self, exchange):
        """处理未完成订单"""
        param = {}
        pass

    def write(self, data_evn, data: str, sid=None):
        if sid is None:
            sid = str(int(time.time() * 1000))

        data_evn.insert(sid, data)

    def update_trading_order(self, data_evn, data):
        # 直接根据order_id更新订单
        # 对于order status 等于 filled的订单需要进行清楚，方便后面遍历未完成订单

        return_orders_info = data[0]["info"]
        order_id = return_orders_info["i"]  # 交易所的订单id
        # symbol = data[0]["symbol"]  # 下单币种
        # place_side = return_orders_info["S"]  # 下单方向
        #
        # order_type = return_orders_info["o"]  # 订单类型，市价单or现价单
        order_current_status = return_orders_info["X"]  # 订单目前最新状态
        # instruct_quantity = return_orders_info["q"]  # 下单量，以合约数量张为单位
        # order_current_filled_amount = return_orders_info["l"]  # 订单目前已经完成的数量，合约张数为单位
        # order_matchmaking_average_price = data[0]["average"]  # 订单成交平均价格
        # order_execuate_fee = data[0]["fee"]["cost"] if data[0]["fee"] else None  # 订单执行手续费，以USDT计价
        # order_current_update_time = data[0]["timestamp"]  # 更新时间， 交易所返回的

        return_orders_info["local_process_time"] = int(time.time() * 1000)  # 记录本地操作时间戳

        if order_current_status == "FILLED" or order_current_status == "CANCELED":
            # 清除数据，避免数据过多
            data_evn.delete(str(order_id))

        else:
            data_evn.update(str(order_id), str(return_orders_info))

        pass

    async def monitor_order_status(self):
        """监控订单状态"""

        record_evn_trade = TradeRecord()
        record_evn_latest = LatestRecord()

        while True:
            try:
                data = await self.exchange.watch_orders()
                pretty_print(f"stutas is {data}")

                self.update_trading_order(record_evn_trade, data)

                self.write(record_evn_latest, str(data), sid="latest_trade")
                # self.local_record.process_update_server_return_orders(data)

                # self.main_engine.put_event(OrderStatusEvent(data=self.local_record.server_return_orders_df))

            except Exception as e:
                traceback.print_exc()

    async def _maker2taker(self):
        """对超时还未成交的maker单进行监控，超时后直接撤单转成taker单"""

        record_evn_trade = TradeRecord()
        loop = asyncio.get_event_loop()

        while True:
            time.sleep(config.maker2taker_delay)

            cancel_task = []
            renew_task = []

            dt = int(time.time() * 1000)
            data = record_evn_trade.search_all()
            for order_id, order_info in data:
                if dt - order_info["local_process_time"] > config.maker2taker_delay * 1000 \
                        and order_info["o"] == "LIMIT":

                    if order_info["X"] == "NEW" or order_info["X"] == "PARTIALLY_FILLED":
                        # 执行撤单和转换成taker单操作
                        cancel_task.append(self.exc_cancel_order(order_id))

                        renew_task.append(self.exc_place_order(symbol=order_info["symbol"],
                                                               orderside=order_info["S"],
                                                               amount=order_info["q"] - order_info["l"],
                                                               price=None,
                                                               type_="MARKET"))

            async def wrapp_task(task1, task2):
                await asyncio.gather(*task1)
                await asyncio.gather(*task2)

            loop.run_until_complete(wrapp_task(cancel_task, renew_task))

    async def exc_place_order(self, symbol, orderside, amount, price=None, type_="LIMIT"):
        """执行下单

        服务器返回的order {'info': {'orderId': 4061408424, 'symbol': 'BTCUSDT', 'status': 'FILLED', 'clientOrderId': 'x-xcKtGhcue56c8502c3b74c87c346bb', 'price': '0.00', 'avgPrice': '61079.81500', 'origQty': '0.080', 'executedQty': '0.080', 'cumQty': '0.080', 'cumQuote': '4886.38520', 'timeInForce': 'GTC', 'type': 'MARKET', 'reduceOnly': False, 'closePosition': False, 'side': 'BUY', 'positionSide': 'BOTH', 'stopPrice': '0.00', 'workingType': 'CONTRACT_PRICE', 'priceProtect': False, 'origType': 'MARKET', 'priceMatch': 'NONE', 'selfTradePreventionMode': 'NONE', 'goodTillDate': 0, 'updateTime': 1728648489381}, 'id': '4061408424', 'clientOrderId': 'x-xcKtGhcue56c8502c3b74c87c346bb', 'timestamp': 1728648489381, 'datetime': '2024-10-11T12:08:09.381Z', 'lastTradeTimestamp': 1728648489381, 'lastUpdateTimestamp': 1728648489381, 'symbol': 'BTC/USDT:USDT', 'type': 'market', 'timeInForce': 'GTC', 'postOnly': False, 'reduceOnly': False, 'side': 'buy', 'price': 61079.815, 'triggerPrice': None, 'amount': 0.08, 'cost': 4886.3852, 'average': 61079.815, 'filled': 0.08, 'remaining': 0.0, 'status': 'closed', 'fee': None, 'trades': [], 'fees': [], 'stopPrice': None, 'takeProfitPrice': None, 'stopLossPrice': None}

        """
        param = {"symbol": symbol,
                 "type": type_,
                 "side": orderside,
                 "amount": amount}

        # 由于存在颞部递归，所以函数内部需要对下单数量和名义价值进行额外校验
        # 以上不需要校验：1）当没有达到最小数量 or 最小名义价值时，直接不进行下单，因为此时金额过小，误差在可控范围内

        try:
            order = await self.exchange.create_order_ws(**param)
            pretty_print(f"feeback is {order}")
            # self.local_record.process_place_order_feedback(order)
            return

        except Exception as e:
            if "Margin is insufficient" in str(e):
                # 当出现保证金不够的情况下，就将子弹全部打出：用递归找出最大量
                # print(e)
                if float(amount) > 2 * self.trading_rules[symbol]["minQty"] and \
                        float(amount) * price > self.trading_rules[symbol]["min_notional"]:
                    await self.exc_place_order(symbol,
                                               orderside,
                                               str(float(amount) - self.trading_rules[symbol]["minQty"]),
                                               price,
                                               type_)
                # traceback.print_exc()

            else:
                print(f"order param is {param}")
                traceback.print_exc()
                return

    async def exc_cancel_order(self, order_id):
        """执行撤单"""
        param = {}
        await self.exchange.cancel_order_ws(order_id)

    async def exc_modify_order(self):
        """修改订单"""
        param = {}
        await self.exchange.edit_order_ws(**param)

    def _start(self):
        p1 = multiprocessing.Process(target=self.on_event)
        p1.start()

        p2 = multiprocessing.Process(target=self._run2)
        p2.start()

    # def _run1(self):
    #     loop = asyncio.new_event_loop()
    #     # loop.create_task(self.on_event(loop))
    #     print("主要循环启动")
    #     loop.run_until_complete(self.on_event(loop))

    def _run2(self):
        loop = asyncio.new_event_loop()
        loop.create_task(self.monitor_order_status())
        loop.run_forever()
